/*
 * Copyright (c) 2008, Takashi Ito
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the authors nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "tansu_internal.h"
#include "tansu_util.h"
#include "tansu_nwif.h"
#include "tansu_http.h"
#include "tansu_ssdp.h"

enum tansu_ssdp_search_target tansu_ssdp_get_search_target(const char *st)
{
    if (strncmp(st, "urn:", 4) == 0) {
        /*                   123456789012345678901234 */
        if (strncmp(&st[4], "schemas-upnp-org:device:", 24) == 0)
            return TANSU_SSDP_ST_UPNP_DEVICE;
        /*                   1234567890123456789012345 */
        if (strncmp(&st[4], "schemas-upnp-org:service:", 25) == 0)
            return TANSU_SSDP_ST_UPNP_SERVICE;

        if (strstr(st, ":device:"))
            return TANSU_SSDP_ST_VENDOR_DEVICE;
        if (strstr(st, ":service:"))
            return TANSU_SSDP_ST_VENDOR_SERVICE;

        return TANSU_SSDP_ST_INVALID;
    }

    if (strcmp(st, "ssdp:all") == 0)
        return TANSU_SSDP_ST_SSDP_ALL;

    if (strcmp(st, "upnp:rootdevice") == 0)
        return TANSU_SSDP_ST_UPNP_ROOTDEVICE;

    if (strncasecmp(st, "uuid:", 5) == 0)
        return TANSU_SSDP_ST_UUID;

    return TANSU_SSDP_ST_INVALID;
}

struct tansu_ssdp_response *tansu_ssdp_response_new(int st_size)
{
    struct tansu_ssdp_response *res;

    if (st_size <= 0)
        return NULL;

    if ((res = calloc(1, sizeof(struct tansu_ssdp_response))) == NULL)
        return NULL;

    if ((res->st = calloc(st_size, sizeof(const char *))) == NULL) {
        free(res);
        return NULL;
    }
    res->st_size = st_size;

    return res;
}

void
tansu_ssdp_response_delete(struct tansu_ssdp_response *res)
{
    if (res) {
        free(res->st);
        free(res);
    }
}

struct tansu_ssdp_ad *tansu_ssdp_ad_new(int nt_size)
{
    struct tansu_ssdp_ad *ad;

    if (nt_size <= 0)
        return NULL;

    if ((ad = calloc(1, sizeof(struct tansu_ssdp_ad))) == NULL)
        return NULL;

    if ((ad->nt = calloc(nt_size, sizeof(const char *))) == NULL) {
        free(ad);
        return NULL;
    }
    ad->nt_size = nt_size;

    return ad;
}

void tansu_ssdp_ad_delete(struct tansu_ssdp_ad *ad)
{
    if (ad) {
        free(ad->nt);
        free(ad);
    }
}

struct tansu_ssdp_request *tansu_ssdp_request_new(const char *st)
{
    struct tansu_ssdp_request *req;

    if ((req = calloc(1, sizeof(struct tansu_ssdp_request))) == NULL)
        return NULL;

    req->mx = TANSU_SSDP_MX_DEFAULT;
    req->count = 1;
    TAILQ_INIT(&req->sockets);
    if (st) {
        if ((req->st = strdup(st)) == NULL) {
            free(req);
            return NULL;
        }
    }

    return req;
}

void tansu_ssdp_request_delete(struct tansu_ssdp_request *req)
{
    if (req) {
        struct tansu_bound_socket *sock;
        struct tansu_keyval *keyval;
        TAILQ_FOREACH(sock, &req->sockets, next) {
            event_del(&sock->bind_ev);
            tansu_socket_close(event_get_fd(&sock->bind_ev));
        }
        TAILQ_FREE_ALL(sock, &req->sockets, next, free);
        if (req->headers) {
            STAILQ_FREE_ALL(keyval, req->headers, next, tansu_keyval_delete);
            free(req->headers);
        }
        free(req->st);
        free(req);
    }
}

struct tansu_bound_socket *
tansu_ssdp_request_get_sock(int fd, struct tansu_ssdp_request *req)
{
    struct tansu_bound_socket *sock;

    TAILQ_FOREACH(sock, &req->sockets, next) {
        if (event_get_fd(&sock->bind_ev) == fd) {
            return sock;
        }
    }

    return NULL;
}

static void
receive_response(int fd, short what, void *arg)
{
    ssize_t ret;
    int n = 3;
    int empty = 0;
    struct tansu_ssdp_request *req = (struct tansu_ssdp_request *)arg;
    struct tansu_bound_socket *sock = tansu_ssdp_request_get_sock(fd, req);
    tansu_status_t status;
    char *buff = req->arena->buff;
    int buffsize = sizeof(req->arena->buff);
    socklen_t len = sizeof(req->arena->packet.remote);

    TASSERT(sock != NULL);

    while (n-- > 0) {
        ret = recvfrom(fd, buff, buffsize, 0,
                       (struct sockaddr*)&req->arena->packet.remote, &len);
        if (ret == -1) {
            if (errno == EAGAIN) {
                empty = 1;
            } else {
                TLOG_ERROR(TLOG_ALL, "recvfrom() failed %d\n", errno);
            }
            break;
        }
        if (ret >= buffsize) {
            break;
        }
        buff[ret] = '\0';
 
        status = tansu_ssdp_packet_parse(&req->arena->packet,
                                         TANSU_SSDP_RESPONSE,
                                         req->arena,
                                         buff);
        if (status == TANSU_SUCCESS && req->cb)
            req->cb(&req->arena->packet, req->cbarg);
    }

    if ((what & EV_TIMEOUT) && empty) {
        tansu_socket_close(fd);
        TAILQ_REMOVE(&req->sockets, sock, next);
        free(sock);
        if (TAILQ_EMPTY(&req->sockets))
            req->finish_cb(req, req->finish_cbarg);
    } else {
        struct timeval timeout;
        ret = tansu_time_get_timeout(&timeout, &req->tv);
        TASSERT(ret == 0);
        event_assign(&sock->bind_ev, event_get_base(&sock->bind_ev),
                     fd, EV_READ, receive_response, req);
        event_add(&sock->bind_ev, &timeout);
    }
}

static void
send_msearch(int fd, short what, void *arg)
{
    int ret;
    struct timeval tv = {1, 0};
    struct tansu_ssdp_request *req = (struct tansu_ssdp_request *)arg;
    struct tansu_bound_socket *sock = tansu_ssdp_request_get_sock(fd, req);
    char *buff = req->arena->buff;
    int buffsize = sizeof(req->arena->buff);

    TASSERT(req);
    TASSERT(sock);

    ret = tansu_ssdp_write_request(buff, buffsize, req->st, req->headers);

    if (ret < buffsize) {
        ret = sendto(fd,
                     buff,
                     ret,
                     MSG_DONTWAIT,
                     (struct sockaddr *)&req->mcast_addr->ss,
                     tansu_socket_length(&req->mcast_addr->ss));
        if (ret == -1)
            TLOG_ERROR(TLOG_SSDP, "sendto() failed %d\n", errno);
    } else {
        TLOG_ERROR(TLOG_SSDP, "tansu_ssdp_write_request() failed\n");
    }

    req->count--;
    if (req->count > 0) {
        event_assign(&sock->bind_ev, NULL, fd, EV_WRITE, send_msearch, req);
        event_add(&sock->bind_ev, &tv);
    } else {
        /* MX sec + 500 msec */
        tv.tv_sec = req->mx;
        tv.tv_usec = (500 * 1000);
        tansu_time_get_monotonic(&req->tv);
        tansu_time_add_msec(&req->tv, (req->mx * 1000) + (500));
        event_assign(&sock->bind_ev, event_get_base(&sock->bind_ev),
                     fd, EV_READ, receive_response, req);
        event_add(&sock->bind_ev, &tv);
    }
}

tansu_status_t
tansu_ssdp_request_msearch(struct event_base *evbase,
                           struct tansu_ssdp_request *req,
                           const struct tansu_nwifs *nwifs)
{
    tansu_status_t ret = TANSU_SUCCESS;
    struct timeval timeout = {1, 0};
    struct tansu_nwif *nwif;
    struct tansu_nwifs *_nwifs = NULL;
    int fd;
    struct tansu_bound_socket *sock;

    if (req == NULL || req->arena == NULL || req->st == NULL || req->mcast_addr == NULL)
        return TANSU_INVALID_ARGUMENT;

    if (nwifs == NULL) {
        if ((_nwifs = tansu_nwif_getaddrs(&ret)) == NULL)
            return ret;
        nwifs = _nwifs;
    }

    STAILQ_FOREACH(nwif, nwifs, entry) {
        /* TODO: IPv6 support */
        if (nwif->family != AF_INET)
            continue;

        if ((sock = calloc(1, sizeof(struct tansu_bound_socket))) == NULL) {
            ret = TANSU_MEMORY_ERROR;
            break;
        }

        fd = tansu_socket_listen(nwif->family, SOCK_DGRAM, nwif->addr, 0, 0, &ret);
        if (fd == -1) {
            free(sock);
            break;
        }
 
        ret = tansu_socket_set_multicast(fd, 1, 4, nwif->addr);
        if (ret != TANSU_SUCCESS) {
            TLOG_ERROR(TLOG_ALL, "tansu_socket_set_multicast() failed %d\n", ret);
            free(sock);
            tansu_socket_close(fd);
            break;
        }

        event_assign(&sock->bind_ev, evbase, fd, EV_WRITE, send_msearch, req);

        TAILQ_INSERT_TAIL(&req->sockets, sock, next);
    }

    if (_nwifs) {
        tansu_nwif_freeaddrs(_nwifs);
    }

    if (ret == TANSU_SUCCESS) {
        TAILQ_FOREACH(sock, &req->sockets, next)
            event_add(&sock->bind_ev, &timeout);
    } else {
        TAILQ_FOREACH(sock, &req->sockets, next)
            tansu_socket_close(event_get_fd(&sock->bind_ev));
        TAILQ_FREE_ALL(sock, &req->sockets, next, free);
    }

    return ret;
}

struct tansu_ssdp_interface *
tansu_ssdp_interface_new(int family, const char *name, const char *mcastif)
{
    tansu_status_t ret;
    struct tansu_ssdp_interface *iface;

    if (name == NULL || mcastif == NULL)
        return NULL;

    if ((iface = calloc(1, sizeof(struct tansu_ssdp_interface))) == NULL)
        return NULL;

    if ((iface->name = strdup(name)) == NULL)
        goto out;

    if ((iface->mcastif = strdup(mcastif)) == NULL)
        goto out;

    iface->fd = tansu_socket_listen(family, SOCK_DGRAM, mcastif, 0, 0, &ret);
    if (iface->fd == -1)
        goto out;

    ret = tansu_socket_set_multicast(iface->fd, 1, TANSU_MULTICAST_TTL, mcastif);
    if (ret == TANSU_SUCCESS)
        return iface;

    tansu_socket_close(iface->fd);

out:
    free(iface->mcastif);
    free(iface->name);
    free(iface);
    return NULL;
}

void
tansu_ssdp_interface_delete(struct tansu_ssdp_interface *iface)
{
    if (iface) {
        tansu_socket_close(iface->fd);
        free(iface->mcastif);
        free(iface->name);
        free(iface);
    }
}

struct tansu_ssdp_server *tansu_ssdp_server_new()
{
    struct tansu_ssdp_server *server;

    if ((server = calloc(1, sizeof(struct tansu_ssdp_server)))) {
        server->recv_fd = -1;
        STAILQ_INIT(&server->responses);
        STAILQ_INIT(&server->listeners);
        STAILQ_INIT(&server->interfaces);
        STAILQ_INIT(&server->ads);
        server->server_name = NULL;
        tansu_socket_set_addr(AF_INET,
                              &server->mcast_addr.ss,
                              TANSU_SSDP_IPv4_ADDR,
                              TANSU_SSDP_IPv4_PORT);
        server->mcast_addr.addr = TANSU_SSDP_IPv4_ADDR;
        // TODO: IPv6 address
    }

    return server;
}

void tansu_ssdp_server_delete(struct tansu_ssdp_server *server)
{
    // TODO: resource cleanup

    if (server) {
        free(server->server_name);
        free(server);
    }
}

tansu_status_t
tansu_ssdp_server_start(struct event_base *evbase,
                        struct tansu_ssdp_server *server,
                        struct tansu_nwifs *nwifs)
{
    int recv_fd;
    tansu_status_t status;

    if (evbase == NULL || server == NULL)
        return TANSU_INVALID_ARGUMENT;

    if (server->recv_fd != -1)
        return TANSU_INVALID_STATE;

    recv_fd = tansu_socket_listen(AF_INET,
                                  SOCK_DGRAM,
                                  TANSU_SSDP_IPv4_ADDR,
                                  1900,
                                  0,
                                  &status);
    if (recv_fd == -1) {
        TLOG_ERROR(TLOG_SSDP, "tansu_socket_listen() failed\n");
        return status;
    }
    tansu_socket_joingroup_all(recv_fd, AF_INET, TANSU_SSDP_IPv4_ADDR, nwifs);

    // TODO: IPv6

    tansu_ssdp_server_create_interfaces(server, nwifs);

    server->evbase = evbase;
    event_assign(&server->recv_ev,
                 evbase,
                 recv_fd,
                 EV_READ | EV_PERSIST,
                 tansu_ssdp_server_read,
                 server);

    if (event_add(&server->recv_ev, NULL) == 0) {
        server->recv_fd = recv_fd;
        return TANSU_SUCCESS;
    }

    if (recv_fd != 1)
        tansu_socket_close(recv_fd);

    return TANSU_FAILED;
}

void
tansu_ssdp_server_stop(struct tansu_ssdp_server *server)
{
    struct tansu_ssdp_response *res;
    struct tansu_ssdp_interface *iface;
    struct tansu_ssdp_ad *ad;

    if (server == NULL)
        return;

    if (server->recv_fd == -1)
        return;

    event_del(&server->recv_ev);
    event_del(&server->send_ev);
    event_del(&server->ad_ev);
    tansu_socket_close(server->recv_fd);
    server->recv_fd = -1;

    STAILQ_FREE_ALL(res, &server->responses, next, tansu_ssdp_response_delete);
    STAILQ_FREE_ALL(iface, &server->interfaces, next, tansu_ssdp_interface_delete);
    STAILQ_FREE_ALL(ad, &server->ads, next, tansu_ssdp_ad_delete);
}

struct tansu_ssdp_listener *
tansu_ssdp_server_add(struct tansu_ssdp_server *server,
                      void (*cb)(const struct tansu_ssdp_packet *, void *),
                      void *cbarg)
{
    struct tansu_ssdp_listener *listener;

    if (server == NULL || cb == NULL)
        return NULL;

    if ((listener = malloc(sizeof(struct tansu_ssdp_listener)))) {
        listener->cb = cb;
        listener->cbarg = cbarg;
        STAILQ_INSERT_TAIL(&server->listeners, listener, next);
    }

    return listener;
}

void
tansu_ssdp_server_remove(struct tansu_ssdp_server *server,
                         struct tansu_ssdp_listener *listener)
{
    if (server && listener) {
        STAILQ_REMOVE(&server->listeners, listener, tansu_ssdp_listener, next);
        free(listener);
    }
}


void
tansu_ssdp_server_nwif_changed(struct tansu_ssdp_server *server,
                               struct tansu_nwif_events *events)
{
    struct tansu_nwif_event *event;
    int fd = event_get_fd(&server->recv_ev);

    STAILQ_FOREACH(event, events, entry) {
        if (event->after == NULL)
            continue;

        if (NWIF_IS_ACTIVE(event->after->flags))
            tansu_socket_joingroup(fd, AF_INET, "239.255.255.250", event->after->addr);
    }
}

void
tansu_ssdp_server_create_interfaces(struct tansu_ssdp_server *server,
                                    struct tansu_nwifs *nwifs)
{
    struct tansu_nwif *nwif;
    struct tansu_ssdp_interface *iface;

    STAILQ_FOREACH(nwif, nwifs, entry) {
#if 0
        if (nwif->family == AF_INET)
            continue;
        if (nwif->family == AF_INET6)
            continue;
#endif
        iface = tansu_ssdp_interface_new(nwif->family, nwif->name, nwif->addr);
        if (iface == NULL)
            continue;

        if (nwif->family == AF_INET)
            iface->mcast_addr = &server->mcast_addr;
        else if (nwif->family == AF_INET6)
            iface->mcast_addr = &server->mcast_addr6;

        STAILQ_INSERT_TAIL(&server->interfaces, iface, next);
    }
}

tansu_status_t
tansu_ssdp_packet_parse(struct tansu_ssdp_packet *packet,
                        enum tansu_ssdp_kind kind,
                        struct tansu_ssdp_arena *arena,
                        char *text)
{
    int count;
    char *line;
    tansu_status_t ret;

    TASSERT(packet && text);

    STAILQ_INIT(&packet->headers);

    if ((line = tansu_buffer_readline(text, &text)) == NULL)
        return TANSU_INVALID_FORMAT;

    if (kind == TANSU_SSDP_REQUEST) {
        packet->kind = TANSU_SSDP_REQUEST;
        ret = tansu_http_parse_request_line(line,
                                            &packet->req.method,
                                            &packet->req.uri,
                                            &packet->req.version);
    } else {
        packet->kind = TANSU_SSDP_RESPONSE;
        ret = tansu_http_parse_response_line(line,
                                             &packet->res.version,
                                             &packet->res.status,
                                             &packet->res.readable);
    }
    if (ret != TANSU_SUCCESS)
        return ret;

    count = 0;
    while ((line = tansu_buffer_readline(text, &text))) {
        char *name;
        char *value;
        if (line[0] == '\0')
            return TANSU_SUCCESS;
        ret = tansu_http_parse_header_line(line, &name, &value);
        if (ret != TANSU_SUCCESS)
            return ret;
        arena->chunk[count].key = name;
        arena->chunk[count].value = value;
        STAILQ_INSERT_TAIL(&packet->headers, &arena->chunk[count], next);
        count++;
        if (count >= (int)TNUMELEM(arena->chunk))
            break;
    }

    return TANSU_INVALID_FORMAT;
}

struct tansu_ssdp_packet *
tansu_ssdp_packet_clone(const struct tansu_ssdp_packet *packet)
{
    tansu_status_t status;
    struct tansu_ssdp_packet *copy;
    struct tansu_keyval *keyval;

    if (packet == NULL)
        return NULL;

    if ((copy = calloc(1, sizeof(struct tansu_ssdp_packet))) == NULL)
        return NULL;

    copy->kind = packet->kind;
    memcpy(&copy->remote, &packet->remote, sizeof(copy->remote));
    if (packet->kind == TANSU_SSDP_REQUEST) {
        if ((copy->req.method = strdup(packet->req.method)) == NULL)
            goto out;
        if ((copy->req.uri = strdup(packet->req.uri)) == NULL)
            goto out;
        if ((copy->req.version = strdup(packet->req.version)) == NULL)
            goto out;
    } else {
        if ((copy->res.version = strdup(packet->res.version)) == NULL)
            goto out;
        copy->res.status = packet->res.status;
        if ((copy->res.readable = strdup(packet->res.readable)) == NULL)
            goto out;
    }

    STAILQ_INIT(&copy->headers);
    status = tansu_keyvals_copy(&copy->headers, &packet->headers);
    if (status == TANSU_SUCCESS)
        return copy;

out:
    if (packet->kind == TANSU_SSDP_REQUEST) {
        free(copy->req.method);
        free(copy->req.uri);
        free(copy->req.version);
    } else {
        free(copy->res.version);
        free(copy->res.readable);
    }
    STAILQ_FREE_ALL(keyval, &copy->headers, next, tansu_keyval_delete);
    free(copy);

    return NULL;
}

void
tansu_ssdp_packet_delete(struct tansu_ssdp_packet *packet)
{
    if (packet == NULL)
        return;

    if (packet->kind == TANSU_SSDP_REQUEST) {
        free(packet->req.method);
        free(packet->req.uri);
        free(packet->req.version);
    } else {
        free(packet->res.version);
        free(packet->res.readable);
    }
    tansu_keyvals_clear(&packet->headers);
    free(packet);
}

static int
__append_headers(char *buff,
                 int buffsize,
                 int pos,
                 const struct tansu_keyvals *headers)
{
    int len;
    const struct tansu_keyval *keyval;

    if (headers) {
        STAILQ_FOREACH(keyval, headers, next) {
            if ((len = buffsize - pos) < 3)
                return buffsize;
            pos += snprintf(&buff[pos], len, "%s: %s", keyval->key, keyval->value);
        };
    }

    if ((len = buffsize - pos) < 3)
        return buffsize;
    pos += snprintf(&buff[pos], len, "\r\n");

    return pos;
}

int
tansu_ssdp_write_request(char *buff,
                         int buffsize,
                         const char *st,
                         const struct tansu_keyvals *headers)
{
    int ret;

#define TANSU_SSDP_MSEARCH_FORMAT \
        "M-SEARCH * HTTP/1.1\r\n" \
        "HOST: 239.255.255.250:1900\r\n" \
        "MAN: \"ssdp:discover\"\r\n" \
        "MX: 1\r\n" \
        "ST: %s\r\n" \

    ret = snprintf(buff,
                   buffsize,
                   TANSU_SSDP_MSEARCH_FORMAT,
                   st);

    return __append_headers(buff, buffsize, ret, headers);
}

int
tansu_ssdp_write_response(char *buff,
                          int buffsize,
                          int max_age,
                          const char *address,
                          int port,
                          const char *server,
                          const char *st,
                          const char *udn,
                          const struct tansu_keyvals *headers)
{
    int ret;
    char date[30];

    if (tansu_get_gmt_iso1123(date, sizeof(date)) == -1)
        return -1;

#define MSEARCH_RESPONSE_FMT \
    "HTTP/1.1 200 OK\r\n" \
    "CACHE-CONTROL: max-age=%d\r\n" \
    "DATE: %s\r\n" \
    "EXT: \r\n" \
    "LOCATION: http://%s:%d/%s\r\n" \
    "SERVER: %s\r\n" \
    "ST: %s\r\n" \
    "USN: %s::%s\r\n" \

    ret = snprintf(buff,
                   buffsize,
                   MSEARCH_RESPONSE_FMT,
                   max_age, date, address, port, udn,
                   server, st, udn, st);

    return __append_headers(buff, buffsize, ret, headers);
}

int
tansu_ssdp_write_notify(char *buff,
                        int buffsize,
                        int max_age,
                        const char *address,
                        int port,
                        const char *server,
                        const char *nt,
                        const char *nts,
                        const char *udn,
                        const struct tansu_keyvals *headers)
{
    int ret;

    // TODO: IPv6

#define NOTIFY_MESSAGE_FMT \
    "NOTIFY * HTTP/1.1\r\n" \
    "HOST: 239.255.255.250:1900\r\n" \
    "CACHE-CONTROL: max-age=%d\r\n" \
    "LOCATION: http://%s:%d/%s\r\n" \
    "NT: %s\r\n" \
    "NTS: %s\r\n" \
    "SERVER: %s\r\n" \
    "USN: %s::%s\r\n" \

    ret = snprintf(buff,
                   buffsize,
                   NOTIFY_MESSAGE_FMT,
                   max_age, address, port, udn,
                   nt, nts, server, udn, nt);

    return __append_headers(buff, buffsize, ret, headers);
}

static void
call_listeners(struct tansu_ssdp_listeners *listeners,
               struct tansu_ssdp_packet *packet)
{
    struct tansu_ssdp_listener *listener;

    STAILQ_FOREACH(listener, listeners, next) {
        listener->cb(packet, listener->cbarg);
    }
}

void
tansu_ssdp_server_read(int fd, short what, void *arg)
{
    tansu_status_t status;
    struct tansu_ssdp_server *server = (struct tansu_ssdp_server *)arg;
    ssize_t ret;
    socklen_t len = sizeof(server->arena.packet.remote);
    int n = 3;

    while (n-- > 0) {
        ret = recvfrom(fd, server->arena.buff, sizeof(server->arena.buff), 0,
                       (struct sockaddr*)&server->arena.packet.remote, &len);
        if (ret == -1) {
            if (errno != EAGAIN)
                TLOG_ERROR(TLOG_SSDP, "recvfrom() failed %d\n", errno);
            break;
        }
        if (ret >= (sizeof(server->arena.buff) - 1))
            break;
        server->arena.buff[ret] = '\0';
 
        status = tansu_ssdp_packet_parse(&server->arena.packet,
                                         TANSU_SSDP_REQUEST,
                                         &server->arena,
                                         server->arena.buff);
        if (status == TANSU_SUCCESS)
            call_listeners(&server->listeners, &server->arena.packet);
    }
}

static void
send_ssdp_packet(int fd, short what, void *arg)
{
    int ret;
    struct timeval tv = {0, 20 * 1000};
    struct tansu_ssdp_server *server = (struct tansu_ssdp_server *)arg;
    struct tansu_ssdp_response *res = STAILQ_FIRST(&server->responses);
    char *buff = server->arena.buff;
    int buffsize = sizeof(server->arena.buff);
    char *address = NULL;

    TASSERT(res);

    do {
        if ((address = tansu_socket_getlocal(&res->remote)) == NULL)
            break;
 
        ret = tansu_ssdp_write_response(buff,
                                        buffsize,
                                        res->max_age,
                                        address,
                                        res->port,
                                        server->server_name,
                                        res->st[res->current],
                                        res->udn,
                                        res->headers);
        if (ret >= buffsize)
            break;
 
        ret = sendto(server->recv_fd,
                     buff,
                     ret,
                     MSG_DONTWAIT,
                     (struct sockaddr *)&res->remote,
                     tansu_socket_length(&res->remote));

        if (ret == -1)
            TLOG_ERROR(TLOG_SSDP, "sendto() failed %d\n", errno);
    } while (0);

    free(address);
    res->current++;

    if (res->current >= res->st_size) {
        STAILQ_REMOVE_HEAD(&server->responses, next);
        tansu_ssdp_response_delete(res);
        if (STAILQ_EMPTY(&server->responses))
            return;
    }

    evtimer_assign(&server->send_ev, server->evbase, send_ssdp_packet, server);
    event_add(&server->send_ev, &tv);
}

void
tansu_ssdp_server_reply(struct tansu_ssdp_server *server,
                        struct tansu_ssdp_response *res)
{
    if (STAILQ_EMPTY(&server->responses)) {
        struct timeval tv = {0, 0};
        evtimer_assign(&server->send_ev, server->evbase,
                       send_ssdp_packet, server);
        event_add(&server->send_ev, &tv);
    }
    STAILQ_INSERT_TAIL(&server->responses, res, next);
}


static void
send_ssdp_notify(int fd, short what, void *arg)
{
    int ret;
    struct timeval tv = {0, 20 * 1000};
    struct tansu_ssdp_server *server = (struct tansu_ssdp_server *)arg;
    struct tansu_ssdp_ad *ad = STAILQ_FIRST(&server->ads);
    struct tansu_ssdp_interface *iface;
    char *buff = server->arena.buff;
    int buffsize = sizeof(server->arena.buff);

    TASSERT(ad);

    STAILQ_FOREACH(iface, &server->interfaces, next) {
        ret = tansu_ssdp_write_notify(buff,
                                      buffsize,
                                      ad->max_age,
                                      iface->mcastif,
                                      ad->port,
                                      server->server_name,
                                      ad->nt[ad->current],
                                      ad->nts,
                                      ad->udn,
                                      ad->headers);
        if (ret >= buffsize)
            continue;

        ret = sendto(iface->fd,
                     buff,
                     ret,
                     MSG_DONTWAIT,
                     (struct sockaddr *)&iface->mcast_addr->ss,
                     tansu_socket_length(&iface->mcast_addr->ss));
 
        if (ret == -1)
            TLOG_ERROR(TLOG_SSDP, "sendto() failed %d\n", errno);
    }

    ad->current++;

    if (ad->current >= ad->nt_size) {
        STAILQ_REMOVE_HEAD(&server->ads, next);
        tansu_ssdp_ad_delete(ad);
        if (STAILQ_EMPTY(&server->ads))
            return;
    }

    evtimer_assign(&server->ad_ev, server->evbase, send_ssdp_notify, server);
    event_add(&server->ad_ev, &tv);
}

void
tansu_ssdp_server_notify(struct tansu_ssdp_server *server,
                         struct tansu_ssdp_ad *ad)
{
    if (STAILQ_EMPTY(&server->ads)) {
        struct timeval tv = {0, 0};
        evtimer_assign(&server->ad_ev, server->evbase,
                       send_ssdp_notify, server);
        event_add(&server->ad_ev, &tv);
    }
    STAILQ_INSERT_TAIL(&server->ads, ad, next);
}

